"""doc"""
import logging
import json
from datetime import timedelta

from django.core.cache import cache
from django.conf import settings
from django.views import View
from django.http import HttpRequest
from django.db import transaction
from django.utils.decorators import method_decorator
from django.utils import timezone
from django.contrib.auth.base_user import AbstractBaseUser

from rest_framework.parsers import JSONParser

from app_common import (
    app_try,
    app_page,
    app_except,
    app_str_check,
    app_uuid,
    json_response_utf8_ok,
    app_sms,
)

from . import (
    models,
    ser,
    wx_api,
    wx_pay,
    access_auth,
    user_pay,
    user_car,
    user_pay_callback,
    user_shopping,
)

logger = logging.getLogger("log")


# wx api
@method_decorator(access_auth.all_user_access, name="get")
@method_decorator(access_auth.all_user_access, name="put")
@method_decorator([app_try], name="post")
class WxUserView(View):
    """doc"""

    def get(self, request: HttpRequest, *args, **kwargs):
        """doc"""
        logger.info(request.META)
        wxuser: models.WxUser = request.user
        m_s = ser.WxUserSer(wxuser)
        return json_response_utf8_ok({"data": m_s.data})

    @transaction.atomic
    def post(self, request: HttpRequest, *args, **kwargs):
        """doc"""
        openid = request.META.get("HTTP_X_WX_OPENID")
        if not openid:
            raise app_except.NullErr(msg="openid is null")

        models.WxUser.objects.select_for_update()
        if models.WxUser.objects.filter(deleted=False).filter(openid=openid).exists():
            raise app_except.WxOpenidExist(msg=openid)

        m = models.WxUser(openid=openid, username=app_uuid.hex_uuid())
        m.save()
        m_s = ser.WxUserSer(m)
        return json_response_utf8_ok({"data": m_s.data})

    @transaction.atomic
    def put(self, request: HttpRequest, *args, **kwargs):
        body: dict = JSONParser().parse(request)
        wxuser: models.WxUser = request.user

        readable_attrs = ["telephone"]
        for attr in readable_attrs:
            if attr in body:
                raise app_except.OnlyReadAttr(msg=attr)

        # 用户微信更改手机号之后，允许重新获取，微信绑定的手机号
        if body.get("cloudID", None):
            telephone = wx_api.get_phone(request, body)
            body.pop("cloudID")
            body["telephone"] = telephone
            body["user_type"] = models.WxUser.UserType.CUSTOM.value

        if body.get("nick_name", None):
            app_str_check.check_name(body["nick_name"])
        if body.get("head_img_url", None):
            app_str_check.check_url(body["head_img_url"])

        m_s = ser.WxUserSer(wxuser, data=body, partial=True)
        m_s.is_valid(raise_exception=True)
        m_s.save()
        return json_response_utf8_ok({"data": m_s.data})


@method_decorator(access_auth.all_user_access, name="dispatch")
class WxUserRechargePromotionView(View):
    """用户查询充值方案"""

    def get(self, request, *args, **kwargs):
        """doc"""
        objs = models.RechargePromotion.objects.filter(deleted=False)
        _objs, num_pages = app_page.page(request, objs)
        m_s = ser.RechargePromotionSer(_objs, many=True)
        return json_response_utf8_ok({"data": m_s.data, "num_pages": num_pages})


@method_decorator(access_auth.custom_or_owner_access, name="dispatch")
class WxUserRemainMoneyView(View):
    """用户充值接口"""

    def get(self, request: HttpRequest, *args, **kwargs):
        """doc"""
        wxuser: models.WxUser = request.user
        objs = models.WxUserRemainMoneyRecord.objects.filter(deleted=False).filter(
            user_uuid=wxuser.uuid
        )
        _objs, num_pages = app_page.page(request, objs)
        m_s = ser.WxUserRechargeRecordSer(_objs, many=True)
        return json_response_utf8_ok({"data": m_s.data, "num_pages": num_pages})

    @transaction.atomic
    def post(self, request: HttpRequest, *args, **kwargs):
        """doc"""
        body: dict = JSONParser().parse(request)
        promotion_uuid = body.pop("promotion_uuid", None)
        if promotion_uuid:
            return user_pay.pay_for_promotion(
                request=request, promotion_uuid=promotion_uuid
            )

        raise app_except.ValueErr("request value error")


@method_decorator([app_try], name="post")
class WxUserPayCallBackView(View):
    """用户微信支付成功后, 回调"""

    @transaction.atomic
    def post(self, request: HttpRequest, *args, **kwargs):
        """doc"""
        # https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/development/pay/callback/

        body: dict = json.loads(request.body)
        logger.info(f"pay_callback request.body: {body}")

        resultCode = body.get("resultCode", "").strip()
        if resultCode != "SUCCESS":
            raise app_except.ValueErr(msg=f"resultCode: {resultCode}")

        openid = body.get("subOpenid", None)
        if not openid:
            raise app_except.NullErr(msg="openid")

        total_fee = int(body.get("totalFee", None))
        if total_fee < 1:
            raise app_except.ParamsErr(msg=f"total_fee: {total_fee}")

        out_trade_no = body.get("outTradeNo", None)
        if not out_trade_no:
            raise app_except.NullErr(msg="out_trade_no")

        upr = (
            models.WxUserPaymentRecord.objects.filter(deleted=False)
            .filter(out_trade_no=out_trade_no)
            .select_for_update()
            .filter(state=models.WxUserPaymentRecord.PaymentState.WAIT.value)
            .first()
        )
        if not upr:
            raise app_except.ObjNotExist(msg=f"out_trade_no={out_trade_no}")

        if upr.pay_for == models.WxUserPaymentRecord.PayFor.PROMOTION.value:
            return user_pay_callback.promotion_callback(upr)

        if upr.pay_for == models.WxUserPaymentRecord.PayFor.SHOPPING.value:
            return user_pay_callback.shopping_callback(upr)

        raise app_except.ValueErr("callback error")


@method_decorator(access_auth.all_user_access, name="dispatch")
class WxUserStoreGoodView(View):
    """管理员管理 wx 用户接口"""

    def get(self, request, *args, **kwargs):
        """doc"""
        objs = models.StoreGood.objects.filter(deleted=False)
        _objs, num_pages = app_page.page(request, objs)
        m_s = ser.StoreGoodSer(_objs, many=True)
        return json_response_utf8_ok({"data": m_s.data, "num_pages": num_pages})


@method_decorator(access_auth.custom_or_owner_access, name="dispatch")
class WxUserStoreGoodCarContentView(View):
    """用户 购物车 接口"""

    def get(self, request, *args, **kwargs):
        """doc"""

        wxuser: models.WxUser = request.user

        objs = models.StoreGoodCarContent.objects.filter(deleted=False).filter(
            user_uuid=wxuser.uuid
        )
        _objs, num_pages = app_page.page(request, objs)
        m_s = ser.StoreGoodCarContentSer(_objs, many=True)
        return json_response_utf8_ok(
            {
                "data": m_s.data,
                "num_pages": num_pages,
                "sum_price": user_pay.get_sum_price(m_s.data),
            }
        )

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        """创建 修改 购物车 内容"""
        wxuser: models.WxUser = request.user
        body: dict = JSONParser().parse(request)
        now = timezone.now()
        _bulk_create = []

        models.StoreGoodCarContent.objects.filter(deleted=False).filter(
            user_uuid=wxuser.uuid
        ).select_for_update()

        for _info in body["list"]:
            _info = dict(_info)
            _info["created_at"] = now
            _info["user_uuid"] = wxuser.uuid

            # check attrs is exists
            _ = _info["good_uuid"]
            _ = _info["pay_count"]

            if (
                not models.StoreGood.objects.filter(deleted=False)
                .filter(uuid=_info["good_uuid"])
                .exists()
            ):
                raise app_except.ObjNotExist(f"商品不存在: {_info['good_uuid']}")

            n = (
                models.StoreGoodCarContent.objects.filter(deleted=False)
                .filter(user_uuid=_info["user_uuid"])
                .filter(good_uuid=_info["good_uuid"])
                .update(pay_count=_info["pay_count"], created_at=_info["created_at"])
            )
            if 0 == n:
                _bulk_create.append(models.StoreGoodCarContent(**_info))

        # this objs return by bulk_create does not have uuid ... fields which created auto
        incomplete_objs = models.StoreGoodCarContent.objects.bulk_create(_bulk_create)

        objs = models.StoreGoodCarContent.objects.filter(deleted=False).filter(
            created_at=now
        )
        _objs, num_pages = app_page.page(request, objs)
        m_s = ser.StoreGoodCarContentSer(_objs, many=True)
        return json_response_utf8_ok({"data": m_s.data, "num_pages": num_pages})

    @transaction.atomic
    def delete(self, request, *args, **kwargs):
        body: dict = JSONParser().parse(request)
        uuids = body["list"]
        n = (
            models.StoreGoodCarContent.objects.filter(deleted=False)
            .filter(uuid__in=uuids)
            .update(deleted=True, del_race=timezone.now())
        )
        if 0 == n:
            raise app_except.ObjNotExist(msg=f"{uuids}")
        return json_response_utf8_ok({"data": n})


@method_decorator(access_auth.custom_or_owner_access, name="dispatch")
class StoreShoppingView(View):
    """用户 购物清单 接口"""

    def get(self, request, *args, **kwargs):
        """doc"""

        wxuser: models.WxUser = request.user

        objs = models.StoreShopping.objects.filter(deleted=False).filter(
            user_uuid=wxuser.uuid
        )
        _objs, num_pages = app_page.page(request, objs)
        m_s = ser.StoreShoppingSer(_objs, many=True)
        return json_response_utf8_ok(
            {
                "data": m_s.data,
                "num_pages": num_pages,
            }
        )

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        """创建 购物清单 内容"""

        def remain_pay(
            _wxuser: models.WxUser, original_price, actual_payment
        ) -> models.StoreShopping:
            # 扣除用户余额
            _wxuser.remain_money -= actual_payment
            _wxuser.save()
            # 发送余额变更短信
            app_sms.send_sms_remain_money_change(
                receiver=_wxuser.telephone,
                change_type=app_sms.SMS_TYPE.CONSUME.value,
                change_money=actual_payment,
                remain_money=_wxuser.remain_money,
            )

            # 创建账单
            pay_type = models.StoreShopping.PayType.REMAIN.value
            state = models.StoreShopping.ShoppingState.PAID.value
            store_shopping = models.StoreShopping(
                user_uuid=wxuser.uuid,
                state=state,
                pay_type=pay_type,
                original_price=original_price,
                actual_payment=actual_payment,
                pay_remain_money=actual_payment,
                pay_weixin=0,
                created_at=now,
            )
            store_shopping.save()

            # 记录消费

            _store_shopping = (
                models.StoreShopping.objects.filter(deleted=False)
                .filter(created_at=now)
                .filter(user_uuid=wxuser.uuid)
                .first()
            )

            models.WxUserRemainMoneyRecord(
                user_uuid=_wxuser.uuid,
                remain_money=_wxuser.remain_money,
                consume=actual_payment,
                shopping_uuid=_store_shopping.uuid,
            ).save()

            if not _store_shopping:
                raise app_except.NullErr(f"_store_shopping: {wxuser.uuid}")
            return store_shopping

        def mix_or_weixin_pay(
            _wxuser: models.WxUser, original_price, actual_payment
        ) -> models.StoreShopping:
            """混合支付 或者 微信支付"""
            # 扣除用户余额
            pay_remain_money = _wxuser.remain_money
            pay_weixin = actual_payment - _wxuser.remain_money

            _wxuser.remain_money = 0
            _wxuser.save()

            # 创建账单
            if pay_remain_money > 0:
                pay_type = models.StoreShopping.PayType.MIX.value
            else:
                pay_type = models.StoreShopping.PayType.WEIXIN.value

            state = models.StoreShopping.ShoppingState.WAIT.value
            store_shopping = models.StoreShopping(
                user_uuid=wxuser.uuid,
                state=state,
                pay_type=pay_type,
                original_price=original_price,
                actual_payment=actual_payment,
                pay_remain_money=pay_remain_money,
                pay_weixin=pay_weixin,
                created_at=now,
            )
            store_shopping.save()
            _store_shopping = (
                models.StoreShopping.objects.filter(deleted=False)
                .filter(created_at=now)
                .filter(user_uuid=wxuser.uuid)
                .first()
            )
            if not _store_shopping:
                raise app_except.NullErr(f"_store_shopping: {wxuser.uuid}")
            return store_shopping

        def create_rela_shopping_content(_shopping, _good_car_content_s):
            _bulk_create_store_shopping_content = []
            for good_car_content in _good_car_content_s.data:
                sum_original_price = (
                    good_car_content["pay_count"] * good_car_content["pay_count"]
                )
                sum_actual_payment = sum_original_price
                _info = {
                    "created_at": now,
                    "user_uuid": wxuser.uuid,
                    "shopping_uuid": _shopping.uuid,
                    "good_uuid": good_car_content["good_uuid"],
                    "pay_count": good_car_content["pay_count"],
                    "original_price": good_car_content["good"]["price"],
                    "sum_original_price": sum_original_price,
                    "sum_actual_payment": sum_actual_payment,
                }
                _bulk_create_store_shopping_content.append(
                    models.StoreShoppingContent(**_info)
                )

            models.StoreShoppingContent.objects.bulk_create(
                _bulk_create_store_shopping_content
            )

        wxuser: models.WxUser = request.user
        now = timezone.now()

        objs = (
            models.StoreGoodCarContent.objects.filter(deleted=False)
            .filter(user_uuid=wxuser.uuid)
            .select_for_update()
        )
        if not objs:
            raise app_except.ValueErr(f"购物车为空, 用户id: {wxuser.uuid}")

        _objs, num_pages = app_page.page(request, objs)

        good_car_content_s = ser.StoreGoodCarContentSer(_objs, many=True)
        original_price = user_pay.get_sum_price(good_car_content_s.data)

        # 打折之后价格
        actual_payment = original_price

        # lock user update remain_money
        models.WxUser.objects.filter(uuid=wxuser.uuid).select_for_update()

        store_shopping = None
        if actual_payment > wxuser.remain_money:
            raise app_except.ValueErr("余额不足请充值")

        store_shopping = remain_pay(wxuser, original_price, actual_payment)

        # 创建关联的购物商品
        create_rela_shopping_content(store_shopping, good_car_content_s)

        # 如果已经支付，那么清空购物车
        if store_shopping.state == models.StoreShopping.ShoppingState.PAID.value:
            clear_count = user_car.clear_user_car(objs)
            if clear_count == 0:
                raise app_except.ValueErr(f"购物车清空失败, 数量为 0, 用户id: {wxuser.uuid}")

        shopping_pay = None
        if store_shopping.pay_type != models.StoreShopping.PayType.REMAIN.value:
            shopping_pay = user_pay.pay_for_shopping(
                request=request, shopping_uuid=store_shopping.uuid
            )

        # 修改商品成交数量
        user_shopping.accumulated_sale_count_of_goods(store_shopping)

        store_shopping_s = ser.StoreShoppingSer(store_shopping)
        return json_response_utf8_ok(
            {
                "data": store_shopping_s.data,
                "shopping_pay": shopping_pay,
                "remain_mony": wxuser.remain_money,
                "num_pages": 1,
            }
        )

    @transaction.atomic
    def delete(self, request, *args, **kwargs):
        wxuser: models.WxUser = request.user
        body: dict = JSONParser().parse(request)
        shopping_uuid = body.pop("shopping_uuid")
        n = (
            models.StoreShopping.objects.filter(deleted=False)
            .filter(uuid=shopping_uuid)
            .filter(user_uuid=wxuser.uuid)
            .update(deleted=True, del_race=timezone.now())
        )

        if 0 == n:
            raise app_except.ObjNotExist(msg=f"购物uuid: {shopping_uuid}")

        n = (
            models.StoreShoppingContent.objects.filter(deleted=False)
            .filter(shopping_uuid=shopping_uuid)
            .update(deleted=True, del_race=timezone.now())
        )

        if 0 == n:
            raise app_except.ObjNotExist(msg=f"清空商品失败, 购物uuid: {shopping_uuid}")

        return json_response_utf8_ok({"data": n})
